/**
 * 
 */
package edu.gatech.oad.mule.view;

import java.awt.BasicStroke;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Point;
import java.io.IOException;
import java.io.InputStream;

import javax.imageio.ImageIO;
import javax.swing.JPanel;

import edu.gatech.oad.mule.view.interfaces.RenderableBuilding;
import edu.gatech.oad.mule.view.interfaces.RenderableBuilding.BuildingType;
import edu.gatech.oad.mule.view.interfaces.RenderableGameDriver;
import edu.gatech.oad.mule.view.interfaces.RenderableMap;
import edu.gatech.oad.mule.view.interfaces.RenderableMap.MapType;
import edu.gatech.oad.mule.view.interfaces.RenderablePlayer;
import edu.gatech.oad.mule.view.interfaces.RenderableTile;
import edu.gatech.oad.mule.view.interfaces.RenderableTile.ProductionType;
import edu.gatech.oad.mule.view.interfaces.RenderableTile.TileType;
import edu.gatech.oad.mule.view.interfaces.RenderableWorldMap;
import edu.gatech.oad.mule.view.interfaces.RenderableZone;

/**
 * Represents the graphics that should be displayed during a land grant or
 * development phase.
 * 
 * @author Dale Avery
 * 
 */
class MapPanel extends JPanel {

	// World map graphics.
	private static Image TILE_PLAINS;
	private static Image TILE_MOUNTAIN1;
	private static Image TILE_MOUNTAIN2;
	private static Image TILE_MOUNTAIN3;
	private static Image TILE_RIVER;
	private static Image TILE_TOWN;
	private static Image TILE_PROD_CRYSTITE;
	private static Image TILE_PROD_ENERGY;
	private static Image TILE_PROD_ORE;
	private static Image TILE_PROD_FOOD;

	// Town map graphics.
	private static Image SCREEN_TOWN;
	private static Image BUILDING_LAND_OFFICE;
	private static Image BUILDING_STABLE;
	private static Image BUILDING_BAR;
	private static Image BUILDING_STORE;
	private static Image BUILDING_OUTFITTER_ORE;
	private static Image BUILDING_OUTFITTER_CRYSTITE;
	private static Image BUILDING_OUTFITTER_FOOD;
	private static Image BUILDING_OUTFITTER_ENERGY;
	private static Image BUILDING_EXIT;

	// Player graphics.
	private static Image PLAYER_HUMAN_STATIONARY;
	private static Image ROBOT_MULE;

	private static int MAP_TILE_WIDTH = 9;
	private static int MAP_TILE_HEIGHT = 5;

	private static float RECTANGLE_THICKNESS = 4f;
	private static float PLAYER_OVAL_THICKNESS = 2f;

	RenderableGameDriver driver;

	/**
	 * Constructs a new IntroPanel.
	 * 
	 */
	public MapPanel(Dimension dimension, RenderableGameDriver driver) {
		try {
			// Save the game driver so that we can render frames.
			this.driver = driver;

			// Attempt to load the graphic for the intro screen.
			ClassLoader classLoader = Thread.currentThread()
					.getContextClassLoader();

			// Load world map graphics.
			InputStream input = classLoader
					.getResourceAsStream("edu/gatech/oad/mule/graphics/screens/TILE_PLAINS.png");
			TILE_PLAINS = ImageIO.read(input);

			input = classLoader
					.getResourceAsStream("edu/gatech/oad/mule/graphics/screens/TILE_MOUNTAIN1.png");
			TILE_MOUNTAIN1 = ImageIO.read(input);

			input = classLoader
					.getResourceAsStream("edu/gatech/oad/mule/graphics/screens/TILE_MOUNTAIN2.png");
			TILE_MOUNTAIN2 = ImageIO.read(input);

			input = classLoader
					.getResourceAsStream("edu/gatech/oad/mule/graphics/screens/TILE_MOUNTAIN3.png");
			TILE_MOUNTAIN3 = ImageIO.read(input);

			input = classLoader
					.getResourceAsStream("edu/gatech/oad/mule/graphics/screens/TILE_RIVER.png");
			TILE_RIVER = ImageIO.read(input);

			input = classLoader
					.getResourceAsStream("edu/gatech/oad/mule/graphics/screens/TILE_TOWN.png");
			TILE_TOWN = ImageIO.read(input);

			// Load production graphics.
			input = classLoader
					.getResourceAsStream("edu/gatech/oad/mule/graphics/screens/TILE_PROD_CRYSTITE.png");
			TILE_PROD_CRYSTITE = ImageIO.read(input);

			input = classLoader
					.getResourceAsStream("edu/gatech/oad/mule/graphics/screens/TILE_PROD_ENERGY.png");
			TILE_PROD_ENERGY = ImageIO.read(input);

			input = classLoader
					.getResourceAsStream("edu/gatech/oad/mule/graphics/screens/TILE_PROD_ORE.png");
			TILE_PROD_ORE = ImageIO.read(input);

			input = classLoader
					.getResourceAsStream("edu/gatech/oad/mule/graphics/screens/TILE_PROD_FOOD.png");
			TILE_PROD_FOOD = ImageIO.read(input);

			// Load town graphics.
			input = classLoader
					.getResourceAsStream("edu/gatech/oad/mule/graphics/screens/SCREEN_TOWN.png");
			SCREEN_TOWN = ImageIO.read(input);

			input = classLoader
					.getResourceAsStream("edu/gatech/oad/mule/graphics/screens/BUILDING_OFFICE.png");
			BUILDING_LAND_OFFICE = ImageIO.read(input);

			input = classLoader
					.getResourceAsStream("edu/gatech/oad/mule/graphics/screens/BUILDING_BAR.png");
			BUILDING_BAR = ImageIO.read(input);

			input = classLoader
					.getResourceAsStream("edu/gatech/oad/mule/graphics/screens/BUILDING_MULE_STABLE.png");
			BUILDING_STABLE = ImageIO.read(input);

			input = classLoader
					.getResourceAsStream("edu/gatech/oad/mule/graphics/screens/BUILDING_STORE.png");
			BUILDING_STORE = ImageIO.read(input);

			input = classLoader
					.getResourceAsStream("edu/gatech/oad/mule/graphics/screens/BUILDING_OUTFITTER_ORE.png");
			BUILDING_OUTFITTER_ORE = ImageIO.read(input);

			input = classLoader
					.getResourceAsStream("edu/gatech/oad/mule/graphics/screens/BUILDING_OUTFITTER_CRYSTITE.png");
			BUILDING_OUTFITTER_CRYSTITE = ImageIO.read(input);

			input = classLoader
					.getResourceAsStream("edu/gatech/oad/mule/graphics/screens/BUILDING_OUTFITTER_ENERGY.png");
			BUILDING_OUTFITTER_ENERGY = ImageIO.read(input);

			input = classLoader
					.getResourceAsStream("edu/gatech/oad/mule/graphics/screens/BUILDING_OUTFITTER_FOOD.png");
			BUILDING_OUTFITTER_FOOD = ImageIO.read(input);

			input = classLoader
					.getResourceAsStream("edu/gatech/oad/mule/graphics/screens/BUILDING_EXIT.png");
			BUILDING_EXIT = ImageIO.read(input);

			// Load player graphics.
			input = classLoader
					.getResourceAsStream(GraphicsLib.PLAYER_HUMAN_STATIONARY);
			PLAYER_HUMAN_STATIONARY = ImageIO.read(input);

			input = classLoader.getResourceAsStream(GraphicsLib.ROBOT_MULE);
			ROBOT_MULE = ImageIO.read(input);

			setPreferredSize(dimension);

		} catch (IOException e) {
			System.out.println("ERROR: failed to create MapPanel");
			e.printStackTrace();
		}
	}

	@Override
	public void paintComponent(Graphics g) {
		// Let UI Delegate paint first, which
		// includes background filling since
		// this component is opaque.

		super.paintComponent(g);
		RenderableMap map = driver.getCurrentMap();

		// Paint the map to the screen.
		if (map.getMapType() == MapType.WORLD) {
			renderWorldMap(g, (RenderableWorldMap) map);
		} else if (map.getMapType() == MapType.TOWN) {
			renderTownMap(g, map);
		} else {
			// TODO throw an error or display an error screen.
		}

		// Paint the players to the screen.
		RenderablePlayer player = driver.getCurrentPlayer();
		renderPlayer(g, player, map);
	}

	/**
	 * Renders a single frame of the player's avatar on the screen.
	 * 
	 * @param g
	 *            The Graphics object to draw to.
	 * @param player
	 *            The player to draw.
	 * @param map
	 *            The map from which position scaling data is obtained.
	 */
	private void renderPlayer(Graphics g, RenderablePlayer player,
			RenderableMap map) {
		// Calculate scaled values to draw player at any resolution
		Dimension dim = map.getSize();
		double positionScaleWidth = (double) this.getWidth() / dim.width;
		double positionScaleHeight = (double) this.getHeight() / dim.height;

		// Scale the player's position and render so that center of player's
		// image appears at scaled position.
		// TODO fix the scaling of the player's image, should be a constant size.
		Point p = player.getLocation();
		int x = (int) (p.x * positionScaleWidth);
		int y = (int) (p.y * positionScaleHeight);
		Image pImage = GraphicsLib.buildPlayerImage(player);
		int centeredX = x - (pImage.getWidth(null) / 2);
		int centeredY = y - (pImage.getHeight(null) / 2);

		g.drawImage(pImage, centeredX, centeredY, pImage.getWidth(null),
				pImage.getHeight(null), null);

		// Draw a mule if the player has one.
		if (player.hasMule()) {
			// TODO fix the scaling on the MULE image.
			g.drawImage(ROBOT_MULE, centeredX, centeredY,
					ROBOT_MULE.getHeight(null) / 2, ROBOT_MULE.getWidth(null) / 2, null);
		}

		// Draw an oval with the player's color to identify the player.
		Graphics2D g2 = (Graphics2D) g;
		g2.setColor(player.getColor());
		g2.setStroke(new BasicStroke(PLAYER_OVAL_THICKNESS));
		g2.drawOval(centeredX, centeredY, pImage.getWidth(null),
				pImage.getHeight(null));
	}

	/**
	 * Renders a single frame of a town map.
	 * 
	 * @param g
	 *            The graphics object to draw the frame to.
	 * @param map
	 *            The map from which frame data is obtained.
	 */
	private void renderTownMap(Graphics g, RenderableMap map) {
		// Render all buildings to the screen.
		RenderableZone[] zones = map.getZones();

		// Draw the background town image to the screen.
		// g.drawImage(SCREEN_TOWN, 0, 0, this.getWidth(), this.getHeight(),
		// null);

		// Calculate scale factors for displaying the town buildings to the
		// screen.
		Dimension dim = map.getSize();
		double positionScaleWidth = (double) this.getWidth() / dim.width;
		double positionScaleHeight = (double) this.getHeight() / dim.height;

		for (RenderableZone zone : zones) {
			if (zone instanceof RenderableBuilding) {

				RenderableBuilding building = (RenderableBuilding) zone;
				int x = (int) (building.getX() * positionScaleWidth);
				int y = (int) (building.getY() * positionScaleHeight);
				int width = (int) (building.getWidth() * positionScaleWidth);
				int height = (int) (building.getHeight() * positionScaleHeight);

				g.drawImage(getImage(building), x, y, width, height, null);
			}
		}

	}

	/**
	 * Renders a single frame of a world map.
	 * 
	 * @param g
	 *            The graphics object to draw the frame to.
	 * @param map
	 *            The map from which frame data is obtained.
	 */
	private void renderWorldMap(Graphics g, RenderableWorldMap map) {
		// Calculate scaled values to draw maps at any resolution.
		int tileWidth = this.getWidth() / MAP_TILE_WIDTH;
		int tileHeight = this.getHeight() / MAP_TILE_HEIGHT;
		Dimension dim = map.getSize();
		double positionScaleWidth = (double) this.getWidth() / dim.width;
		double positionScaleHeight = (double) this.getHeight() / dim.height;

		// Render all tiles to the screen.
		RenderableTile[] tiles = map.getTiles();

		for (RenderableTile tile : tiles) {
			int x = (int) (tile.getX() * positionScaleWidth);
			int y = (int) (tile.getY() * positionScaleHeight);

			// Draw the base layer of the tile.
			g.drawImage(getImage(tile), x, y, tileWidth, tileHeight, null);

			// Draw the production on the tile
			if (tile.getProductionType() != ProductionType.NO_MULE)
				g.drawImage(getProductionImage(tile), x, y, tileWidth,
						tileHeight, null);

			if (tile.getOwnerColor() != null) {
				Graphics2D g2 = (Graphics2D) g;
				g2.setColor(tile.getOwnerColor());
				g2.setStroke(new BasicStroke(RECTANGLE_THICKNESS));
				g2.drawRect((int) (x + RECTANGLE_THICKNESS),
						(int) (y + RECTANGLE_THICKNESS),
						(int) (tileWidth - 2 * RECTANGLE_THICKNESS),
						(int) (tileHeight - 2 * RECTANGLE_THICKNESS));
			}
		}
	}

	/**
	 * @param tile
	 *            A tile for which an image is needed.
	 * @return The image that corresponds to this tile type.
	 */
	private Image getImage(RenderableTile tile) {
		if (tile.getTileType() == TileType.PLAINS)
			return TILE_PLAINS;
		else if (tile.getTileType() == TileType.MOUNTAINS_1)
			return TILE_MOUNTAIN1;
		else if (tile.getTileType() == TileType.MOUNTAINS_2)
			return TILE_MOUNTAIN2;
		else if (tile.getTileType() == TileType.MOUNTAINS_3)
			return TILE_MOUNTAIN3;
		else if (tile.getTileType() == TileType.RIVER)
			return TILE_RIVER;
		else if (tile.getTileType() == TileType.TOWN)
			return TILE_TOWN;
		else
			return TILE_TOWN;
	}

	/**
	 * @param tile
	 *            A tile for which a production image is needed.
	 * @return The image that corresponds to this tile's production type.
	 */
	private Image getProductionImage(RenderableTile tile) {
		if (tile.getProductionType() == ProductionType.CRYSTITE)
			return TILE_PROD_CRYSTITE;
		else if (tile.getProductionType() == ProductionType.ENERGY)
			return TILE_PROD_ENERGY;
		else if (tile.getProductionType() == ProductionType.SMITHORE)
			return TILE_PROD_ORE;
		else if (tile.getProductionType() == ProductionType.FOOD)
			return TILE_PROD_FOOD;
		else
			return null;
	}

	/**
	 * @param building
	 *            The building for which an image is needed.
	 * @return The Image associated with that building type or a default image.
	 */
	private Image getImage(RenderableBuilding building) {
		if (building.getBuildingType() == BuildingType.BAR)
			return BUILDING_BAR;
		else if (building.getBuildingType() == BuildingType.EXIT)
			return BUILDING_EXIT;
		else if (building.getBuildingType() == BuildingType.STABLE)
			return BUILDING_STABLE;
		else if (building.getBuildingType() == BuildingType.LAND_OFFICE)
			return BUILDING_LAND_OFFICE;
		else if (building.getBuildingType() == BuildingType.STORE)
			return BUILDING_STORE;
		else if (building.getBuildingType() == BuildingType.OUTFITTER_ORE)
			return BUILDING_OUTFITTER_ORE;
		else if (building.getBuildingType() == BuildingType.OUTFITTER_ENERGY)
			return BUILDING_OUTFITTER_ENERGY;
		else if (building.getBuildingType() == BuildingType.OUTFITTER_FOOD)
			return BUILDING_OUTFITTER_FOOD;
		else if (building.getBuildingType() == BuildingType.OUTFITTER_CRYSTITE)
			return BUILDING_OUTFITTER_CRYSTITE;
		else
			return BUILDING_OUTFITTER_ORE;
	}
}
